﻿using System;
using System.Collections.Generic;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;

namespace Avaxo.Scripting
{
    internal abstract class DynamicLanguageEngine : DynamicLanguageEngineBase
    {
        private IScriptModule currentModule;
        private DynamicLanguageCodeSenseProvider codeSenseProvider;

        #region ILanguageEngine Members

        public override bool IsExpressionValid( string expression )
        {
            if ( string.IsNullOrEmpty( expression ) )
            {
                return false;
            }

            SourceCodeProperties props = ScriptEngine.GetCodeProperties( expression, SourceCodeKind.Expression );
            return props == SourceCodeProperties.None;
        }

        public override bool IsStatementComplete( string statement )
        {
            try
            {
                SourceCodeProperties props = ScriptEngine.GetCodeProperties(statement, SourceCodeKind.Statements);
                return props == SourceCodeProperties.None;

            }
            catch ( Exception )
            {
                return true;
            }
        }

        public override Version Version
        {
            get { return new Version( ScriptEngine.VersionString ); }
        }

        public override IScriptModule CurrentModule
        {
            get
            {
                if (  currentModule == null )
                {
                    currentModule = ScriptDomainManager.CurrentManager.Host.DefaultModule;
                }
                return currentModule;
            }
        }

        public override ICodeSenseProvider CodeSenseProvider
        {
            get
            {
                if (codeSenseProvider == null)
                {
                    codeSenseProvider = new DynamicLanguageCodeSenseProvider( ScriptEngine, CurrentModule );
                }
                return codeSenseProvider;
            }
        }

        #endregion

        private class DynamicLanguageCodeSenseProvider : CodeSenseProvider
        {
            public DynamicLanguageCodeSenseProvider( IScriptEngine engine, IScriptModule module ) : base( engine, module )
            {
            }

            public override string GetFunctionSignature( string name )
            {
                if ( !string.IsNullOrEmpty( name ) )
                {
                    object obj = GetCurrentObject( name );
                    if (obj != null && Engine.IsObjectCallable(obj, Module))
                    {
                        return Engine.GetObjectDocumentation( obj );
                    }
                }

                return null;
            }

            public override string[] GetMemberNames( string name )
            {
                if (!string.IsNullOrEmpty(name))
                {
                    object obj = GetCurrentObject( name );
                    if ( obj != null )
                    {
                        return Engine.GetObjectMemberNames( obj );
                    }
                }

                return new string[0];
            }

            public override string GetFunctionDoc( string name )
            {
                throw new NotImplementedException();
            }

            private object GetCurrentObject( string name )
            {
                SourceUnit sourceUnit = SourceUnit.CreateSnippet( Engine, name );
                //TODO: cache the tokenCategorizer
                ITokenCategorizer tokenCategorizer = Engine.LanguageProvider.GetTokenCategorizer();
                tokenCategorizer.Initialize( null, sourceUnit.GetReader(), new SourceLocation( 0, 1, 1 ) );
                List<TokenInfo> tokens = new List<TokenInfo>( tokenCategorizer.ReadTokens( name.Length ) );
                if ( tokens[ 0 ].Category == TokenCategory.Identifier )
                {
                    TokenInfo token = tokens[ 0 ];
                    tokens.RemoveAt( 0 );
                    object currentObject;
                    if ( Engine.TryGetVariable( name.Substring( 0, token.SourceSpan.End.Index ), Module, out currentObject ) )
                    {
                        while ( tokens.Count > 0 )
                        {
                            token = tokens[ 0 ];
                            tokens.RemoveAt( 0 );
                            string tokenText = name.Substring( token.SourceSpan.Start.Index,
                                                               token.SourceSpan.End.Index - token.SourceSpan.Start.Index );
                            if ( token.Category == TokenCategory.Identifier )
                            {
                                object memberValue;
                                if ( Engine.TryGetObjectMemberValue( currentObject, tokenText, Module, out memberValue ) )
                                {
                                    currentObject = memberValue;
                                }
                                else
                                {
                                    return null;
                                }
                            }
                            else if ( token.Category == TokenCategory.EndOfStream )
                            {
                                return currentObject;
                            }
                            else if ( token.Category == TokenCategory.WhiteSpace )
                            {
                                //The Python token categorizer returns an extra whitespace token at the end of single line statements
                                continue;
                            }
                            else if ( token.Trigger == TokenTriggers.MemberSelect )
                            {
                                continue;
                            }
                            else
                            {
                                return null;
                            }
                        }
                        return currentObject;
                    }
                }

                return null;
            }
        }
    }
}